/**
 *	Copyright (c) 2009, SpLib.net
 * 	All rights reserved.
  	
 * 	Author:6spring
 * 	About:Socket连接类
 * 			Diy型将抛出NetEvent（RECV)
 * 			否则解析至SpLib.Net.NetMsgMgr
*/

package SpLib.Net
{
	import SpLib.Com.Com;
	import SpLib.Com.SpLibEvent;
	import SpLib.Tools.CRC32;
	import SpLib.Tools.Tr;
	
	import com.adobe.serialization.json.JSON;
	
	import flash.errors.*;
	import flash.events.*;
	import flash.net.Socket;
	import flash.utils.Timer;
	import flash.utils.getTimer;
	
	public class Socket extends EventDispatcher
	{
		public static const ACTPING:int = 30*1000;
		
		private var m_hSocket:flash.net.Socket = null;
		private var m_sLinkIP:String = "";
		private var m_nLinkPort:int;
		
		private var m_PingTimer:Timer;
		private var m_ConnectTimer:Timer;
		private var m_ConnState:int;
		private var m_nFrameNum:int;
		
		private var m_SendBuf:String;			//数据区
		private var m_RecvBuf:String;			//数据区
		
		private var m_bTraceSocket:Boolean = false;
		private var m_bDiySocket:Boolean = true;
		
		private var m_nVersion:int;	
		public var m_GLID:SpLib.Net.GLID = new SpLib.Net.GLID;
		private var m_nOPKey:uint;
		
		private var m_sCharset:String;
		private var m_bUseKey:Boolean = false;
		private var m_bEncrypt:Boolean = false;
		private var m_bUseZip:Boolean = false;
		private var m_bBase64:Boolean = false;
		
		private var m_nPakFrameNum:int				= 0;
		
		private var m_nPing:int;
		
		public function Socket(diy:Boolean=false)
		{
			m_ConnState = 0;
			m_nFrameNum = 0;
			m_nPing = 65535;
			m_SendBuf = "";
			m_RecvBuf = "";
			m_bDiySocket = diy;
			m_bTraceSocket = false;
		}
		
		public function get ping():int
		{
			return m_nPing;
		}
		
		public function set tracesocket(tracesocket:Boolean):void
		{
			m_bTraceSocket = tracesocket;
		}
		
		public function get connected():Boolean
		{
			return m_ConnState==3;
		}
				
		public function Send(pak:SpLib.Net.NetPak):void
		{
			if(m_hSocket==null) return;
			
			m_nPakFrameNum++;
			pak.m_nFrameNum = m_nPakFrameNum;
			m_SendBuf = pak.GetSendStr(m_nOPKey,m_sCharset,m_bEncrypt,m_bBase64,m_bUseZip);
			m_hSocket.writeMultiByte(m_SendBuf,m_sCharset);
			m_hSocket.flush();
		}
		
		private function OnRecvData(charset:String):Array
		{
			var src:String = m_hSocket.readMultiByte(m_hSocket.bytesAvailable,charset);
			m_RecvBuf += src;
			
			var ph:String = NetPak.PAK_HEADER;
			var pt:String = NetPak.PAK_TAIL;
			var headidx:int,tailidx:int;
			var paks:Array = new Array;
			
			while(m_RecvBuf.length>0)
			{
				headidx = m_RecvBuf.indexOf(ph);
				if(headidx<0)
				{
					//错误的包头，跳过一字节重试
					m_RecvBuf = m_RecvBuf.substr(1);
					continue;
				} 
				
				tailidx = m_RecvBuf.indexOf(pt);
				//包尾不完整，待下一包
				if(tailidx<=0) break;
		
				var datastr:String = m_RecvBuf.substr(headidx+ph.length,tailidx);
				var msg:Object = JSON.decode(datastr);
				var netpak:NetPak = ParsePak(msg);
				if(netpak!=null)
				{
					paks.push(netpak);
				}
				
				m_RecvBuf = m_RecvBuf.substr(tailidx+pt.length);
			}
			
			return paks;
		}
		
		private function ParsePak(msg:Object):SpLib.Net.NetPak
		{
			if(msg==null || !msg.hasOwnProperty("d") || msg.d==null)
			{
				return null;
			}
			
			var netpak:NetPak = new NetPak();
			
			if(typeof(msg.d)=="object")
			{
				if(netpak.SetAsJson(msg.d))
				{
					return netpak;
				}
				
				return null;
			}
			
			if(!m_bBase64) return null;
			
			var zip:Boolean = false;
			if(msg.hasOwnProperty("z") && msg.z!=null && msg.z==1)
			{
				zip = true;
			}
			
			if(typeof(msg.d)=="string")
			{
				if(netpak.SetAsPakStr(msg.k,msg.d,m_sCharset,m_bEncrypt,zip))
				{
					return netpak;
				}
				
				return null;
			}
			
			return null;
		}
		
		public function Connect(ip:String,port:int,outtime:int=30000):void
		{
			if(m_ConnState==1)
			{
				return;
			}
			
			m_ConnState = 1;
			
			m_sLinkIP = ip;
			m_nLinkPort = port;
			
			if(m_ConnectTimer!=null)
			{
				m_ConnectTimer.stop();
				m_ConnectTimer = null;
			}
				
			m_ConnectTimer =  new Timer(outtime);
			m_ConnectTimer.addEventListener(TimerEvent.TIMER,OnConnectFail);
			m_ConnectTimer.start();
	
			if(m_hSocket==null)
			{
				m_hSocket = new flash.net.Socket();
				m_hSocket.addEventListener(Event.CONNECT,OnConnect);
				m_hSocket.addEventListener(IOErrorEvent.IO_ERROR,OnIOError);
        		m_hSocket.addEventListener(SecurityErrorEvent.SECURITY_ERROR,OnSecurityError);
			}
			
			m_SendBuf = "";
			m_RecvBuf = "";
			
			if(!m_bTraceSocket)
			{
				SpLib.Tools.Tr.SysNote("正在连接 " + ip + ":" + port.toString() + "...");
			}
			
			m_hSocket.connect(ip,port);
		}
		
		private function OnIOError(event:IOErrorEvent):void
		{
		}
		
		private function OnSecurityError(event:SecurityErrorEvent):void
		{
		}
		
		public function Disconnect():void
		{
			if(m_hSocket!=null)
			{
				m_hSocket.close();
				m_hSocket = null;
			}
			
			if(m_ConnectTimer!=null)
			{
				m_ConnectTimer.stop();
				m_ConnectTimer = null;
			}
			
			if(m_PingTimer!=null)
			{
				m_PingTimer.stop();
				m_PingTimer = null;
			}
			
			m_ConnState = 0;
		}
		
		private function OnConnectFail(evt:Event):void
		{
			m_hSocket = null;
			
			m_ConnectTimer.stop();
			m_ConnectTimer = null;
			m_ConnState = 0;
			
			if(!m_bTraceSocket)
			{
				SpLib.Tools.Tr.Error("连接 " + m_sLinkIP + ":" + m_nLinkPort.toString() + "超时");
			}
			
			dispatchEvent(new SpLib.Net.NetEvent(SpLib.Net.NetEvent.NET_CONNECTFAIL));
		}
		
		private function OnConnect(evt:Event):void
		{
			m_ConnectTimer.stop();
			m_ConnectTimer = null;
			
			m_ConnState = 2;
			//连接成功，发送握手信息
			m_hSocket.addEventListener(ProgressEvent.SOCKET_DATA,OnRecvHand);
			
			if(!m_bTraceSocket)
			{
				SpLib.Tools.Tr.SysNote("连接成功，发送握手信息");
			}
			
			var data:Object = new Object;
			data.t = NetMsg.TYPE_HAND;
			data.ver = 0x00010000;
			data.type = 0;
			data.charset = SpLib.Com.Com.DEFCHARSET;
			var hand:String = NetPak.HAND_HEADER + JSON.encode(data) + NetPak.PAK_TAIL;
			m_hSocket.writeMultiByte(hand,SpLib.Com.Com.DEFCHARSET);
			m_hSocket.flush();
		}
		
		private function ConnectFail():void
		{
			if(m_hSocket!=null)
			{
				m_hSocket.close();
			}
					
			if(m_ConnectTimer!=null)
			{
				m_ConnectTimer.stop();
				m_ConnectTimer = null;
			}
			
			m_ConnState = 0;
			dispatchEvent(new SpLib.Net.NetEvent(SpLib.Net.NetEvent.NET_CONNECTFAIL));
		}
		
		//握手协议返回
		private function OnRecvHand(e:Event):void
		{
			var src:String = m_hSocket.readMultiByte(m_hSocket.bytesAvailable,SpLib.Com.Com.DEFCHARSET);
			m_RecvBuf += src;
			
			var ph:String = NetPak.PAK_HEADER;
			var pt:String = NetPak.PAK_TAIL;
			var headidx:int,tailidx:int;
			var datastr:String;
			
			if(m_RecvBuf.length>0)
			{
				//连接握手协议头
				headidx = m_RecvBuf.indexOf(ph);
				if(headidx!=0)
				{
					ConnectFail();
					return;
				}
				
				tailidx = m_RecvBuf.indexOf(pt);
				//包尾不完整，待下一包
				if(tailidx<=0) return;
				
				datastr = m_RecvBuf.substr(headidx+ph.length,tailidx);
				m_RecvBuf = m_RecvBuf.substr(tailidx+pt.length);
			}
					
			var resp:Object = JSON.decode(datastr);
			if(resp==null)
			{
				ConnectFail();
				return;
			}
			
			if(!m_bTraceSocket)
			{
				SpLib.Tools.Tr.SysNote("握手协议返回");
			}
			
			m_hSocket.removeEventListener(ProgressEvent.SOCKET_DATA,OnRecvHand);
			m_ConnState = 3;
			m_nPakFrameNum = 0;
			
			m_nVersion = resp.svrver;
			m_GLID.SetAsArray(resp.linkid);
			m_sCharset = resp.charset;
			m_bUseKey = resp.usekey;
			
			if(resp.hasOwnProperty("encrypt") && resp.encrypt!=null)
			{
				m_bEncrypt = resp.encrypt;
			}
			else
			{
				m_bEncrypt = false;
			}
			
			if(resp.hasOwnProperty("zip") && resp.zip!=null)
			{
				m_bUseZip = resp.zip;
			}
			else
			{
				m_bUseZip = false;
			}
					
			if(resp.hasOwnProperty("base64") && resp.base64!=null)
			{
				m_bBase64 = resp.base64;
			}
			else
			{
				m_bBase64 = false;
			}
			
			//有压缩或加密，则必定Base64
			if(m_bEncrypt || m_bUseZip)
			{
				m_bBase64 = true;
			}
			
			m_nOPKey = SpLib.Tools.CRC32.Make(0xCF6C8C64,m_GLID.GetAsByteArray());
				
			if(!m_bDiySocket)
			{
				SpLib.Net.NetMsgMgr.AddSocket(m_GLID,this);
			}
			
			m_hSocket.addEventListener(ProgressEvent.SOCKET_DATA,OnRecv);
			m_hSocket.addEventListener(Event.CLOSE,OnClose);
				
			m_PingTimer = new Timer(ACTPING);
			m_PingTimer.addEventListener(TimerEvent.TIMER,SendPing);
			m_PingTimer.start();
			
			var pak:SpLib.Net.NetPak = new SpLib.Net.NetPak;
			pak.m_Msg = new SpLib.Net.NetMsg;
			pak.m_Msg.m_nMsgtype = SpLib.Net.NetMsg.TYPE_PING;
			pak.m_Msg.m_nEveID = getTimer();
			Send(pak);
			
			dispatchEvent(new SpLibEvent(SpLibEvent.NET_CONNECTED));
		}
		
		private function SendPing(evt:Event):void
		{
			var pak:SpLib.Net.NetPak = new SpLib.Net.NetPak;
			pak.m_Msg = new SpLib.Net.NetMsg;
			pak.m_Msg.m_nMsgtype = SpLib.Net.NetMsg.TYPE_PING;
			pak.m_Msg.m_nEveID = getTimer();
			Send(pak);
		}
		
		private function OnRecv(e:Event):void
		{
			var paks:Array = OnRecvData(m_sCharset);
			for each(var pak:NetPak in paks)
			{	
				if(pak.m_Msg.m_nMsgtype!=NetMsg.TYPE_PING)
				{
					pak.m_Msg.m_GLID = m_GLID;
					if(!m_bDiySocket)
					{
						SpLib.Net.NetMsgMgr.RecvMsg(pak.m_Msg);
					}
					else
					{
						this.dispatchEvent(new SpLib.Net.NetEvent(SpLib.Net.NetEvent.NET_RECV,pak));
					}
				}
				else
				{
					m_nPing = ( getTimer() - pak.m_Msg.m_nEveID)/2;
				}
			}
		}
		
		private function OnClose(e:Event):void
		{
			if(!m_bTraceSocket)
			{
				SpLib.Tools.Tr.Error("连接 " + m_sLinkIP + ":" + m_nLinkPort.toString() + "关闭");
			}
			
			m_ConnState = 0;
			m_PingTimer.start();
			m_PingTimer = null;
			m_nPing = 65535;
			
			m_SendBuf = "";
			m_RecvBuf = "";
			
			if(!m_GLID.IsEmpty())
			{
				SpLib.Net.NetMsgMgr.DelSocket(m_GLID);
				m_GLID.Clear();
			}
			
			if(m_PingTimer!=null)
			{
				m_PingTimer.stop();
				m_PingTimer = null;
			}
	
			m_hSocket = null;
			
			dispatchEvent(new SpLib.Net.NetEvent(SpLib.Net.NetEvent.NET_BREAK));
		}
	}
}